package cc.verywell.pureblock.msg.nfs.strc;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;

import cc.verywell.pureblock.net.AioProvider;
import cc.verywell.pureblock.util.unsafe.UnsafeRun;

/**
 *<h1>File Structure V0</h1>
* <pre>
* [bit_1] head;{
*   & 001 links:
*     [bit_2] linksArray.length
*     [bit_4] linksArray.totalsize
*     {
*       [bit_2] length = (link.hash+link.priority)
*       {
*         [bit_1] link.priority
*         [bit_rest] link
*       }
*     }
*   & 010 alias:
*     [bit_2] aliasArray.length
*     {
*       [bit_2] alias.length
*       {
*         [bit_rest] alias
*       }
*     }
*   & 100 signs:
*     [bit_2] signsArray.length
*     {
*       [bit_2] pubKey.length
*       {
*         [bit_rest] pubKey
*       }
*       [bit_2] sign.length
*       {
*         [bit_rest] sign
*       }
*     }
*    & 01111000 >>> 3 unUsedHead
*    & 10000000 unSupported : throw new Err;
* }
* [bit_rest] data;
* 
* </pre>
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年5月16日 上午4:57:31
*/
@Deprecated
public class NfsStructureV0 implements EncodeAble{
	final static public byte DIRECTLINK_PRIORITY = 0;

	final private byte[] payload;
	final public Sub data;
	final public long linksSize;
	final public Link[] links;
	final public Sub[] alias;
	final public Sign[] signs;
	final public byte unUsedHead;
	
	/**
	 * @return bb = new Bytebuffer ();
	 * <br>bb.position = dataOffset; 
	 * <br>bb.limit = dataLen+dataOffset;
	 * <br>bb.capacity = payload.length;
	 */
	public interface Sub extends EncodeAble{
		public int getLength();
		public byte[] getHash();
		public ByteBuffer getByteBuffer();
		public static int ARRAY_HEAD_SIZE = 2;
	}
	public interface Link extends Sub{
		public byte getPriority();
		public static int SINGLE_EXTRA_SIZE = 2+1;
		public static int ARRAY_HEAD_SIZE = 2+8;
	}
	static public class Sign implements EncodeAble{
		private Sub pubKey;
		private Sub sign;
		public Sign(Sub pubKey,Sub sign) {
			this.pubKey = pubKey;
			this.sign = sign;
		}
		public Sub getPubKey() {
			return pubKey;
		}
		public Sub getSign() {
			return sign;
		}
		@Override
		public int encodeSize() {
			return pubKey.encodeSize()+sign.encodeSize();
		}
		@Override
		public void encode(ByteBuffer bb) {
			pubKey.encode(bb);
			sign.encode(bb);
		}
		public static int ARRAY_HEAD_SIZE = 2;
	}
	//////Read
	public long readMemoryUsed() {
		long l = 0;
		l += 40+UnsafeRun.MemoryAddressSize*5;
		l += ((12+payload.length-1)/8+1)*8;
		if(data!=null) l += 32;
		if(links!=null) l += links.length*(UnsafeRun.MemoryAddressSize+48)+16;
		if(alias!=null) l += alias.length*(UnsafeRun.MemoryAddressSize+32)+16;
		if(signs!=null) l += signs.length*(UnsafeRun.MemoryAddressSize+8)+signs.length*(UnsafeRun.MemoryAddressSize+32)*2+16;
		return l;
	}
	public NfsStructureV0(byte[] payload) {
		this.payload = payload;
		if(payload==null)throw new NullPointerException();
		ByteBuffer bb = ByteBuffer.wrap(payload);
		byte dataHead = bb.get();
		{
			///分析主体
			if(0!=(0b10000000 & dataHead))throw new IllegalArgumentException(Integer.toBinaryString(dataHead));
			
			if(0!=(0b00000001 & dataHead)) {
				linksSize = bb.getLong();
				int i = 0xFFFF & bb.getShort();
				LinkRead[] sub = new LinkRead[i];
				for(i=0;i<sub.length;i++) {
					int j = 0xFFFF & bb.getShort();
					int pos = bb.position();
					bb.position(pos+j);
					byte k  = bb.get();
					sub[i] = new LinkRead(pos,j,k);
				}
				links = sub;
			}else {
				linksSize = 0;
				links = null;
			}
			if(0!=(0b00000010 & dataHead)) {
				int i = 0xFFFF & bb.getShort();
				SubRead[] sub = new SubRead[i];
				for(i=0;i<sub.length;i++) {
					int j = 0xFFFF &  bb.getShort();
					sub[i] = new SubRead(bb.position(),j);
					bb.position(bb.position()+j);
				}
				alias = sub;
			}else {
				alias = null;
			}
			if(0!=(0b00000100 & dataHead)) {
				int i = 0xFFFF &  bb.getShort();
				Sign[] sub = new Sign[i];
				for(i=0;i<sub.length;i++) {
					int j;
					j = 0xFFFF & bb.getShort();
					SubRead pubKey = new SubRead(bb.position(),j);
					bb.position(bb.position()+j);
					j = 0xFFFF & bb.getShort();
					SubRead sign = new SubRead(bb.position(),j);
					bb.position(bb.position()+j);
					sub[i] = new Sign(pubKey,sign);
				}
				signs = sub;
			}else {
				signs = null;
			}
			if(bb.hasRemaining()) {
				data = new SubDataRead(bb.position(), bb.remaining());
			}else {
				data = null;
			}
			unUsedHead = (byte)((0b01111000 & dataHead) >>> 3);
		}
	}
	public class SubRead implements Sub{
		protected int offset;
		protected int len;
		public int getLength() {
			return len;
		}
		private byte[] buf;
		public byte[] getHash() {
			byte[] bb = buf;
			if(bb!=null)return bb;
			bb = Arrays.copyOfRange(payload, offset, len);
			buf = bb; 
			return bb;
		}
		public ByteBuffer getByteBuffer() {
			if(len==0)return null;
			return ByteBuffer.wrap(payload, offset, len);
		}
		public SubRead(int offset, int len) {
			this.offset = offset;
			this.len = len;
		}
		@Override
		public int encodeSize() {
			return 2+len;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putShort((short)len);
			bb.put(payload, offset, len);
		}
	}
	public class SubDataRead extends SubRead{
		public SubDataRead(int offset, int len) {
			super(offset, len);
		}
		@Override
		public int encodeSize() {
			return len;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.put(payload, offset, len);
		}
	}
	public class LinkRead extends SubRead implements Link{
		protected byte priority;
		public LinkRead(int offset, int len, byte priority) {
			super(offset, len);
			this.priority = priority;
		}
		public byte getPriority() {
			return this.priority;
		}
		@Override
		public int encodeSize() {
			return Link.SINGLE_EXTRA_SIZE+len;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putShort((short)(1+len));
			bb.put(payload, offset, len);
			bb.put(priority);
		}
	}

	
	////write
	@Override
	public int encodeSize() {
		return payload.length;
	}
	@Override
	public void encode(ByteBuffer bb) {
		bb.put(payload);
	}
	//static
	/**
	 * 
	 * @param fileSize main DATA size
	 * @param sublinks sub links
	 * @param alias 
	 * @param signs
	 * @return total blocks need;
	 */
	static public int encodeBlocks(long fileSize, List<Link> sublinks, List<Sub> alias, List<Sign> signs) {
		final int MAXSIZE = AioProvider.FILE_MAX_SIZE,BLOCKHEAD = 1,LINK_HASH_SIZE = 32/*SHA256*/;
		int blocks = 0;
		int l = BLOCKHEAD,k;//head
		if(alias!=null) {
			int head_size = Sub.ARRAY_HEAD_SIZE;
			l+= head_size ;
			if(l>MAXSIZE){
				//l-=head_size;
				blocks++;
				l = BLOCKHEAD+head_size;
			}
			for (EncodeAble ea : alias) {
				l+= k = ea.encodeSize();
				if(l>MAXSIZE){
					//l-=k;
					blocks++;
					l = BLOCKHEAD+head_size+k;
				}
			}
		}
		if(signs!=null) {
			int head_size = Sign.ARRAY_HEAD_SIZE;
			
			l+= head_size ;
			if(l>MAXSIZE){
				//l-=head_size;
				blocks++;
				l = BLOCKHEAD+head_size;
			}
			for (EncodeAble ea : signs) {
				l+= k = ea.encodeSize();
				if(l>MAXSIZE){
					//l-=k;
					blocks++;
					l = BLOCKHEAD+head_size+k;
				}
			}
		}
		int head_size = Link.ARRAY_HEAD_SIZE;
		boolean nolinkhead = true;
		if(sublinks!=null) {
			if(nolinkhead) {
				l+= head_size ;
				nolinkhead = false;
				if(l>MAXSIZE){
					//l-=head_size;
					blocks++;
					l = BLOCKHEAD+head_size;
				}
			}
			for (EncodeAble ea : sublinks) {
				l+= k = ea.encodeSize();
				if(l>MAXSIZE){
					//l-=k;
					blocks++;
					l = BLOCKHEAD+head_size+k;
				}
			}
		}
		//// blocks 
		int single_size = Link.SINGLE_EXTRA_SIZE+LINK_HASH_SIZE;
		blocks += (int)(fileSize/(MAXSIZE-BLOCKHEAD));
		if(blocks>0) {
			int bked = 0;
			if(nolinkhead) {
				l+= head_size ;
				nolinkhead = false;
				if(l>MAXSIZE){
					//l-=head_size;
					blocks++;
					l = BLOCKHEAD+head_size;
				}
			}
			while(bked<blocks) {
				l+= k = single_size;
				bked++;
				if(l>MAXSIZE){
					//l-=k;
					blocks++;
					l = BLOCKHEAD+head_size+k;
				}
			}
		}
		//rest
		blocks++; //root block added;
		int restSize = (int)(fileSize % (MAXSIZE-BLOCKHEAD) );
		if(l>=restSize)return blocks;
		blocks++; //rest block added;
		k = single_size;
		if(nolinkhead) k+=head_size;
		l+= k;
		if(l<=MAXSIZE)return blocks;
		blocks++;//another addblock added , this block included two links;
		return blocks;
	}
	static public void encode(ByteBuffer bb, byte[] data, long linksSize, List<Link> links, List<Sub> alias, List<Sign> signs, byte unUsedHead) {
		//TODO
		int head = 0;
		if(links!=null)head |= 0b00000001;
		if(alias!=null)head |= 0b00000010;
		if(signs!=null)head |= 0b00000100;
		if(unUsedHead!=0)head |= ((0xF & unUsedHead)<<3);
		bb.put((byte)head);
			
		if(links!=null){
			bb.putLong(linksSize);
			bb.putShort((short)links.size());
			for (EncodeAble ea : links)ea.encode(bb);
		}
		if(alias!=null){
			bb.putShort((short)alias.size());
			for (EncodeAble ea : alias)ea.encode(bb);
		}
		if(signs!=null){
			bb.putShort((short)signs.size());
			for (EncodeAble ea : signs)ea.encode(bb);
		}
		if(data!=null){
			bb.put(data);
		}
	}
	static public int encodeSize(int dataSize, List<Link> links, List<Sub> alias, List<Sign> signs) {
		int l = dataSize;
		//if(data!=null)l+=data.encodeSize();
		if(links!=null){
			l+= Link.ARRAY_HEAD_SIZE;
			for (EncodeAble ea : links)l+=ea.encodeSize();
		}
		if(alias!=null){
			l+= Sub.ARRAY_HEAD_SIZE;
			for (EncodeAble ea : alias)l+=ea.encodeSize();
		}
		if(signs!=null){
			l+= Sign.ARRAY_HEAD_SIZE;
			for (EncodeAble ea : signs)l+=ea.encodeSize();
		}
		return l;
	}
	static public class SubWrite implements Sub{
		public int getLength() {
			return buf.length;
		}
		final protected byte[] buf;
		public byte[] getHash() {
			return buf;
		}
		public ByteBuffer getByteBuffer() {
			return ByteBuffer.wrap(buf);
		}
		public SubWrite(byte[] buf) {
			this.buf = buf;
		}
		@Override
		public int encodeSize() {
			return 2+buf.length;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putShort((short)buf.length);
			bb.put(buf);
		}
	}
	static public class LinkWrite extends SubWrite implements Link{
		final protected byte priority;
		public LinkWrite(byte[] buf, byte priority) {
			super(buf);
			this.priority = priority;
		}
		public byte getPriority() {
			return this.priority;
		}
		@Override
		public int encodeSize() {
			return Link.SINGLE_EXTRA_SIZE+buf.length;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putShort((short)buf.length);
			bb.put(buf);
			bb.put(priority);
		}
	}

}
